home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / url.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-02  |  32.6 KB  |  1,334 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     url.c
  4.  
  5.     This module handles parsing and opening URLs.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <ctype.h>
  14. #include <stdlib.h>
  15. #include <Processes.h>
  16.  
  17. #include "glob.h"
  18. #include "url.h"
  19. #include "article.h"
  20. #include "strutil.h"
  21. #include "dialog.h"
  22. #include "message.h"
  23. #include "apputil.h"
  24. #include "fileutil.h"
  25. #include "newswatcher.h"
  26. #include "memutil.h"
  27. #include "resutil.h"
  28. #include "sfutil.h"
  29. #include "wind.h"
  30. #include "full.h"
  31. #include "subject.h"
  32. #include "ic.h"
  33.  
  34.  
  35.  
  36. #define isurlschemechar(c)                (isalnum((c)) || c == '+' || c == '.' || c == '-')
  37.     
  38. #define kOptionClickDlg                    162
  39. #define kCantFindHelperDlgID            153
  40.  
  41. #define kFetchCreatorType                'FTCh'
  42. #define kFetchMinVersionNumber            0x02120000
  43. #define kFetchMinVersionNumberStr        "2.1.2"
  44.  
  45. #define kAnarchieCreatorType            'Arch'
  46. #define kAnarchieMinVersionNumber        0x01200000
  47. #define kAnarchieMinVersionNumberStr    "1.2.0"
  48. #define kAnarchieMinVersionNumberGURL    0x01402003
  49.  
  50. #define kFTPHelperURLFileType            'AURL'
  51.  
  52. #define kNetscapeCreatorType            'MOSS'
  53. #define kNetscapeMinVersionNumber        0x00096003
  54. #define kNetscapeMinVersionNumberStr    "0.0.9b3"
  55.  
  56. #define kMacWebCreatorType                'MWEB'
  57. #define kMacWebMinVersionNumber            0x00000000
  58. #define kMacWebMinVersionNumberStr        ""
  59.  
  60. #define kMacWAISCreatorType                'MWAS'
  61. #define kMacWAISMinVersionNumber        0x01298000
  62. #define kMacWAISMinVersionNumberStr        "1.2.9"
  63.  
  64. #define kMacWebAndWAISOpenURLEventClass    'wwwc'
  65. #define kMacWebAndWAISOpenURLEventID    'ourl'
  66. #define kMacWebAndWAISOpenURLKeyword    'kURL'
  67.  
  68. #define kNCSAMosaicCreatorType            'MOS!'
  69. #define kNCSAMosaicMinVersionNumber        0x02004008
  70. #define kNCSAMosaicMinVersionNumberStr    "2.0a8"
  71. #define kNCSAMosaicOpenURLEventClass    'mos!'
  72. #define kNCSAMosaicOpenURLEventID        'ourl'
  73.  
  74. #define kTurboGopherCreatorType            'TGOF'
  75. #define kTurboGopherMinVersionNumber    0x02004001
  76. #define kTurboGopherMinVersionNumberStr    "2.0a1"
  77. #define kTurboGopherMinVersionNumberCanon    0x02006001
  78.  
  79. #define kNCSATelnetCreatorType            'NCSA'
  80. #define kNCSATelnetSettingsFileType        'CONF'
  81. #define kNCSATelnetMinVersionNumber        0x02400000
  82. #define kNCSATelnetMinVersionNumberStr    "2.4"
  83.  
  84. #define ktn3270CreatorType                'GFTM'
  85. #define ktn3270SettingsFileType            'GFTS'
  86. #define ktn3270MinVersionNumber            0x02402007
  87. #define ktn3270MinVersionNumberStr        "2.4d7"
  88.  
  89. #define kFingerCreatorType                'PnLF'
  90. #define kFingerMinVersionNumber            0x01500000
  91. #define kFingerMinVersionNumberStr        "1.5.0"
  92.  
  93.  
  94.  
  95. typedef char *TURLSchemeName;
  96.  
  97. static TURLSchemeName gURLSchemeNames[] = {
  98.     ":",        /* kNotURL */
  99.     "mailto:",    
  100.     "news:",
  101.     "nntp:",
  102.     "ftp:",
  103.     "http:",
  104.     "gopher:",
  105.     "wais:",
  106.     "telnet:",
  107.     "tn3270:",
  108.     "finger:",
  109.     "whois:",
  110.     nil
  111. };
  112.  
  113. static TURLHelperInfoPtr gURLInfo[] = {
  114.     nil,    /* kNotURL */
  115.     nil,     /* kMailtoURL */
  116.     nil,     /* kNewsURL */
  117.     nil,     /* kNntpURL */
  118.     &gPrefs.ftpHelperInfo,
  119.     &gPrefs.httpHelperInfo,
  120.     &gPrefs.gopherHelperInfo,
  121.     &gPrefs.waisHelperInfo,
  122.     &gPrefs.telnetHelperInfo,
  123.     &gPrefs.tn3270HelperInfo,
  124.     &gPrefs.fingerHelperInfo,
  125.     &gPrefs.whoisHelperInfo
  126. };
  127.  
  128. typedef struct THelperInfo {
  129.     OSType sig;                                /* signature of helper program */
  130.     unsigned long minVersionNumber;            /* min version number of helper program */
  131.     char *minVersionStr;                    /* min version number as a string */
  132. } THelperInfo;
  133.  
  134. static THelperInfo gHelperInfo[] = {
  135.     {kAnarchieCreatorType, kAnarchieMinVersionNumber,
  136.         kAnarchieMinVersionNumberStr},
  137.     {kFetchCreatorType, kFetchMinVersionNumber,
  138.         kFetchMinVersionNumberStr},
  139.     {kNetscapeCreatorType, kNetscapeMinVersionNumber,
  140.         kNetscapeMinVersionNumberStr},
  141.     {kMacWebCreatorType, kMacWebMinVersionNumber,
  142.         kMacWebMinVersionNumberStr},
  143.     {kMacWAISCreatorType, kMacWAISMinVersionNumber,
  144.         kMacWAISMinVersionNumberStr},
  145.     {kNCSAMosaicCreatorType, kNCSAMosaicMinVersionNumber,
  146.         kNCSAMosaicMinVersionNumberStr},
  147.     {kTurboGopherCreatorType, kTurboGopherMinVersionNumber,
  148.         kTurboGopherMinVersionNumberStr},
  149.     {kNCSATelnetCreatorType, kNCSATelnetMinVersionNumber,
  150.         kNCSATelnetMinVersionNumberStr},
  151.     {ktn3270CreatorType, ktn3270MinVersionNumber,
  152.         ktn3270MinVersionNumberStr},
  153.     {kFingerCreatorType, kFingerMinVersionNumber,
  154.         kFingerMinVersionNumberStr},
  155.     {0, 0, nil}
  156. };
  157.  
  158. typedef OSType TDefaultHelperInfo[5];
  159.  
  160. static TDefaultHelperInfo gDefaultHelperInfo[] = {
  161.     /* kNotURL */        {0, 0, 0, 0, 0},
  162.     /* kEmailtoURL */    {0, 0, 0, 0, 0},
  163.     /* kNewsURL */        {0, 0, 0, 0, 0},
  164.     /* kNntpURL */        {0, 0, 0, 0, 0},
  165.     /* kFtpURL */        {kFetchCreatorType, kAnarchieCreatorType, 0, 0, 0},
  166.     /* kHttpURL */        {kMacWebCreatorType, kNCSAMosaicCreatorType, kNetscapeCreatorType,
  167.                             0, 0},
  168.     /* kGopherURL */    {kTurboGopherCreatorType, kMacWebCreatorType, kNCSAMosaicCreatorType, 
  169.                             kNetscapeCreatorType, 0},
  170.     /* kWaisURL */        {kMacWAISCreatorType, kNCSAMosaicCreatorType, 0, 0, 0},
  171.     /* kTelnetURL */    {kNCSATelnetCreatorType, 0, 0, 0, 0},
  172.     /* ktn3270URL */    {ktn3270CreatorType, 0, 0, 0, 0},
  173.     /* kFingerURL */    {kFingerCreatorType, 0, 0, 0, 0},
  174.     /* kWhoisURL */        {kFingerCreatorType, 0, 0, 0, 0}
  175. };    
  176.  
  177.  
  178.  
  179. /*----------------------------------------------------------------------------
  180.     FindOneURLHelper 
  181.     
  182.     Search for a URL helper program.
  183.     
  184.     Entry:    sig = signature of helper.
  185.             minVersion = minimum version number of helper, or 0 if none.
  186.             
  187.     Exit:    function result = error code.
  188.             *helperInfo = helper info. If no helper is found, the sig is
  189.                 set to 0.
  190. ----------------------------------------------------------------------------*/
  191.  
  192. static OSErr FindOneURLHelper (OSType sig, unsigned long minVersion,
  193.     TURLHelperInfoPtr helperInfo)
  194. {
  195.     OSErr err = noErr;
  196.     FSSpec fSpec;
  197.     unsigned long versionNumber = 0;
  198.     
  199.     err = FindAppFromSig(sig, &fSpec, nil, nil);
  200.     if (err != noErr) goto exit;
  201.     if (minVersion != 0) {
  202.         err = GetAppVersionNumber(&fSpec, &versionNumber);
  203.         if (err != noErr) goto exit;
  204.         if (versionNumber < minVersion) goto exit;
  205.     }
  206.     helperInfo->sig = sig;
  207.     helperInfo->versionNumber = versionNumber;
  208.     err = GetLastModDateTime(&fSpec, &helperInfo->lastMod);
  209.     if (err != noErr) goto exit;
  210.     
  211.     return noErr;
  212.     
  213. exit:
  214.  
  215.     helperInfo->sig = 0;
  216.     return noErr;
  217. }
  218.  
  219.  
  220.  
  221. /*----------------------------------------------------------------------------
  222.     FindURLHelper 
  223.     
  224.     Search for an appropriate default URL helper, if one hasn't been
  225.     found already.
  226.     
  227.     Entry:    urlKind = kind of url.
  228.             
  229.     Exit:    function result = true if helper found.
  230.             gPrefs fields for helper set.
  231. ----------------------------------------------------------------------------*/
  232.  
  233. Boolean FindURLHelper (TURLKind urlKind)
  234. {
  235.     OSErr err = noErr;
  236.     TURLHelperInfoPtr prefsInfo;
  237.     OSType *defaultHelpers;
  238.     OSType sig;
  239.     short i, j;
  240.     Boolean foundHelper = false;
  241.  
  242.     prefsInfo = gURLInfo[urlKind];
  243.     if (prefsInfo->sig == 0) {
  244.         defaultHelpers = gDefaultHelperInfo[urlKind];
  245.         for (i = 0; ; i++) {
  246.             sig = defaultHelpers[i];
  247.             if (sig == 0) break;
  248.             for (j = 0; ; j++) {
  249.                 if (sig == gHelperInfo[j].sig) break;
  250.             }
  251.             err = FindOneURLHelper(sig, gHelperInfo[j].minVersionNumber, prefsInfo);
  252.             if (err == noErr && prefsInfo->sig != 0){
  253.                 foundHelper = true;
  254.                 break;
  255.             }
  256.         }
  257.     } else {
  258.         foundHelper = true;
  259.     }
  260.     return foundHelper;
  261. }
  262.  
  263.  
  264.  
  265. /*----------------------------------------------------------------------------
  266.     ValidURLHelper 
  267.     
  268.     Check a file to see if it is a valid URL helper program.
  269.     
  270.     Entry:    urlKind = kind of URL.
  271.             fSpec = pointer to application file spec.
  272.             
  273.     Exit:    function result = true if valid.
  274.             *helperInfo = helper info.
  275.             
  276.     This function is called when the user selects a new URL helper in the
  277.     preferences dialog. It checks minimum version numbers for the known
  278.     URL helper programs. If the helper is valid, the helper info is filled
  279.     in.
  280.     
  281.     This function is also recalled whenever the user runs a URL helper
  282.     and the last mod date/time of the helper as recorded in the helper info
  283.     does not match the last mod date/time of the file being executed.
  284. ----------------------------------------------------------------------------*/
  285.  
  286. Boolean ValidURLHelper (FSSpec *fSpec, TURLHelperInfoPtr helperInfo)
  287. {
  288.     OSErr err = noErr;
  289.     FInfo fInfo;
  290.     OSType sig, sig2;
  291.     unsigned long versionNumber = 0;
  292.     unsigned long minVersionNumber = 0;
  293.     unsigned long lastMod;
  294.     short j;
  295.     CStr255 fmt, msg;
  296.     
  297.     err = FSpGetFInfo(fSpec, &fInfo);
  298.     if (err != noErr) goto exit;
  299.     sig = fInfo.fdCreator;
  300.     
  301.     for (j = 0; ; j++) {
  302.         sig2 = gHelperInfo[j].sig;
  303.         if (sig2 == 0 || sig == sig2) break;
  304.     }
  305.     if (sig2 != 0) minVersionNumber = gHelperInfo[j].minVersionNumber;
  306.  
  307.     if (minVersionNumber != 0) {
  308.         err = GetAppVersionNumber(fSpec, &versionNumber);
  309.         if (err != noErr) goto exit;
  310.         if (versionNumber < minVersionNumber) {
  311.             GetCString(kStrHelperTooOld, fmt);
  312.             p2cstr(fSpec->name);
  313.             sprintf(msg, fmt, fSpec->name, gHelperInfo[j].minVersionStr);
  314.             c2pstr((char*)fSpec->name);
  315.             StopAlertMessage(msg);
  316.             return false;
  317.         }
  318.     }
  319.     
  320.     err = GetLastModDateTime(fSpec, &lastMod);
  321.     if (err != noErr) goto exit;
  322.  
  323.     helperInfo->sig = sig;
  324.     helperInfo->versionNumber = versionNumber;
  325.     helperInfo->lastMod = lastMod;
  326.     
  327.     return true;
  328.     
  329. exit:
  330.  
  331.     GetCString(kStrUnexpectedErr, fmt);
  332.     sprintf(msg, fmt, err);
  333.     StopAlertMessage(msg);
  334.     return false;
  335. }
  336.  
  337.  
  338.  
  339. /*----------------------------------------------------------------------------
  340.     DoCantFindHelperDialog 
  341.     
  342.     Present a "can't find helper program" dialog.
  343.     
  344.     Entry:    msg = error message.
  345. ----------------------------------------------------------------------------*/
  346.  
  347. void DoCantFindHelperDialog (char *msg)
  348. {
  349.     DialogPtr dlg;
  350.     short item;
  351.     OSErr err = noErr;
  352.  
  353.     c2pstr(msg);
  354.     ParamText((StringPtr)msg, "\p", "\p", "\p");
  355.     p2cstr((StringPtr)msg);
  356.     err = MyGetNewDialog(kCantFindHelperDlgID, ok, 0, &dlg);
  357.     SysBeep(0);
  358.     if (err != noErr) return;
  359.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  360.     DoClose(dlg);
  361. }
  362.  
  363.  
  364.  
  365. /*----------------------------------------------------------------------------
  366.     HelperErrorMessage
  367.     
  368.     Issue a helper program error message.
  369.     
  370.     Entry:    err = error number.
  371.             sig = helper program signature.
  372.     
  373.     Exit:    function result = userCanceledErr.
  374. ----------------------------------------------------------------------------*/
  375.  
  376. static OSErr HelperErrorMessage (OSErr err, OSType sig)
  377. {
  378.     CStr255 fmt, msg;
  379.     Str31 name;
  380.  
  381.     *name = 0;
  382.     FindAppNameFromSig(sig, name);
  383.     p2cstr(name);
  384.     if (err == fnfErr) {
  385.         GetCString(kStrURLHelperNotFound, fmt);
  386.         sprintf(msg, fmt, name);
  387.         DoCantFindHelperDialog(msg);
  388.     } else if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
  389.         GetCString(kStrURLHelperNoMem, fmt);
  390.         sprintf(msg, fmt, name);
  391.         ErrorMessage(msg);
  392.     } else {
  393.         GetCString(kStrURLHelperUnexpectedErr, fmt);
  394.         sprintf(msg, fmt, err, name);
  395.         ErrorMessage(msg);
  396.     }
  397.     return userCanceledErr;
  398. }
  399.  
  400.  
  401.  
  402. /*----------------------------------------------------------------------------
  403.     IsSlackMailtoURL
  404.     
  405.     Check for a slack emailto URL.
  406.     
  407.     Entry:    text = handle to text.
  408.             begin = offset in text of beginning of object.
  409.             end = offset in text of end of object.
  410.             
  411.     Exit:    function result = true if slack emailto URL.
  412.             url = parsed URL.
  413. ----------------------------------------------------------------------------*/
  414.  
  415. static Boolean IsSlackMailtoURL (Handle text, short begin, short end, CStr255 url)
  416. {
  417.     char *p, *q, *x, *y, *textEnd;
  418.     long len;
  419.     Boolean couldBeEmailAddress = false;
  420.     
  421.     p = *text + begin;
  422.     q = *text + end;
  423.     textEnd = *text + MyGetHandleSize(text);
  424.     
  425.     if (*p == '<') {
  426.         x = p - 1;
  427.         while (x > *text && *x != CR) x--;
  428.         if (x > *text) x++;
  429.         if (MyStrNEqual(x, "From", 4)) {
  430.             y = x+4;
  431.         } else if (MyStrNEqual(x, "Reply-To", 8)) {
  432.             y = x+8;
  433.         } else {
  434.             y = nil;
  435.         }
  436.         if (y != nil) {
  437.             while (isLWSP(*y) && y < p) y++;
  438.             if (y < p && *y == ':') couldBeEmailAddress = true;
  439.         } else {
  440.             y = q + 1;
  441.             while (y < textEnd && isLWSP(*y)) y++;
  442.             if (y + 6 <= textEnd && MyStrNEqual(y, "wrote:", 6))
  443.                 couldBeEmailAddress = true;
  444.         }
  445.         if (!couldBeEmailAddress) return false;
  446.     }
  447.     
  448.     x = p;
  449.     while (*x != '@' && x < q) x++;
  450.     if (x >= q) return false;
  451.     while (*x != '.' && x < q) x++;
  452.     if (x >= q) return false;
  453.     
  454.     if (*p == '<') {
  455.         p++;
  456.         q--;
  457.     }
  458.     len = q - p + 1;
  459.     if (len == 0 || len > 255) return false;
  460.     BlockMoveData(p, url, len);
  461.     url[len] = 0;
  462.     return true;
  463. }
  464.  
  465.  
  466.  
  467. /*----------------------------------------------------------------------------
  468.     IsSlackNewsURL
  469.     
  470.     Check for a slack news URL.
  471.     
  472.     Entry:    text = handle to text.
  473.             begin = offset in text of beginning of object.
  474.             end = offset in text of end of object.
  475.             
  476.     Exit:    function result = true if slack news URL.
  477.             url = parsed URL.
  478. ----------------------------------------------------------------------------*/
  479.  
  480. static Boolean IsSlackNewsURL (Handle text, short begin, short end, CStr255 url)
  481. {
  482.     char *p, *q, *x;
  483.     long len;
  484.     
  485.     p = *text + begin;
  486.     q = *text + end;
  487.     
  488.     x = p;
  489.     if (*x != '<') return false;
  490.     while (*x != '@' && x < q) x++;
  491.     if (x >= q) return false;
  492.     while (*x != '.' && x < q) x++;
  493.     if (x >= q) return false;
  494.  
  495.     len = q - p + 1;
  496.     if (len == 0 || len > 255) return false;
  497.     BlockMoveData(p, url, len);
  498.     url[len] = 0;
  499.     return true;
  500. }
  501.  
  502.  
  503.  
  504. /*----------------------------------------------------------------------------
  505.     ParseURL
  506.     
  507.     Parse a URL.
  508.     
  509.     Entry:    text = handle to text.
  510.             begin = offset in text of beginning of object.
  511.             end = offset in text of end of object.
  512.             
  513.     Exit:    function result = kind of URL.
  514.             url = parsed URL.
  515.             *urlBegin = offset in text of beginning of parsed URL.
  516.             *urlEnd = offset in text of end of parsed URL.
  517.             
  518.     If begin == end, ParseURL tries to locate the beginning and end of 
  519.     the URL.
  520. ----------------------------------------------------------------------------*/
  521.  
  522. static TURLKind ParseURL (Handle text, short begin, short end, CStr255 url,
  523.     short *urlBegin, short *urlEnd)
  524. {
  525.     char *textEnd, *p, *q, *x, *y, *a, *b;
  526.     long len;
  527.     TURLKind urlKind;
  528.     Boolean isMultiLineURL = false, mightBeMultiLineURL = false;
  529.     short numCR;
  530.     char *schemeName;
  531.     
  532.     p = *text + begin;
  533.     q = *text + end - 1;
  534.     
  535.     if (begin == end) {
  536.     
  537.         textEnd = *text + MyGetHandleSize(text);
  538.         
  539.         numCR = 0;
  540.         x = p;
  541.         y = q;
  542.         while (true) {
  543.             while (x >= *text && *x != '<' && *x != '>' && 
  544.                 *x != '"' && *x != CR) x--;
  545.             if (x < *text || *x == '>' || *x == '"') break;
  546.             if (*x == CR) {
  547.                 numCR++;
  548.                 if (numCR > 10) break;
  549.                 x--;
  550.             } else { /* *x == '<' */
  551.                 a = x+1;
  552.                 while (a < textEnd && isurlschemechar(*a)) a++;
  553.                 mightBeMultiLineURL = a < textEnd && *a == ':';
  554.                 break;
  555.             }
  556.         }
  557.         if (mightBeMultiLineURL) {
  558.             numCR = 0;
  559.             while (true) {
  560.                 while (y < textEnd && *y != '<' && *y != '>' && 
  561.                     *y != '"' && *y != CR) y++;
  562.                 if (y >= textEnd || *y == '<' || *y == '"') break;
  563.                 if (*y == CR) {
  564.                     numCR++;
  565.                     if (numCR > 10) break;
  566.                     y++;
  567.                 } else { /* *y == '>' */
  568.                     isMultiLineURL = true;
  569.                     break;
  570.                 }
  571.             }
  572.         }
  573.         
  574.         if (isMultiLineURL) {
  575.             p = x;
  576.             q = y;
  577.         } else {
  578.             while (p >= *text && !isLWSPorCR(*p) && *p != '<' && *p != '"') p--;
  579.             if (*p != '<') p++;
  580.             while (q < textEnd && !isLWSPorCR(*q) && *q != '>' && *q != '"') q++;
  581.             if (*q != '>') q--;
  582.         }
  583.         
  584.         if (p >= q) return kNotURL;
  585.     
  586.     }
  587.     
  588.     while (p < q && isLWSPorCR(*p)) p++;
  589.     while (p < q && isLWSPorCR(*q)) q--;
  590.     
  591.     if (*p == '<') {
  592.         if (*q != '>') return kNotURL;
  593.     } else {
  594.         if (*q == '>') return kNotURL;
  595.     } 
  596.     
  597.     *urlBegin = p - *text;
  598.     *urlEnd = q - *text + 1;
  599.     x = p;
  600.     if (*x == '<') x++;
  601.     y = x;
  602.     while (isurlschemechar(*y) && y < q) y++;
  603.     if (*y == ':') {
  604.         if (MyStrNEqual(x, "url:", 4)) x += 4;
  605.         if (*q == '>') q--;
  606.         len = q - x + 1;
  607.         if (len == 0 || len > 255) return kNotURL;
  608.         BlockMoveData(x, url, len);
  609.         url[len] = 0;
  610.         b = url;
  611.         while (*b != ':' && *b != 0) b++;
  612.         if (*b == ':') {
  613.             for (a = url; a < b; a++) *a = tolower(*a);
  614.         }
  615.         for (urlKind = kNotURL; ; urlKind++) {
  616.             schemeName = gURLSchemeNames[urlKind];
  617.             if (schemeName == nil) return kNotURL;
  618.             if (MyStrNEqual(url, schemeName, strlen(schemeName))) goto exit;
  619.         }
  620.     }
  621.     
  622.     begin = p - *text;
  623.     end = q - *text;
  624.     urlKind = kMailtoURL;
  625.     if (IsSlackMailtoURL(text, begin, end, url)) goto exit;
  626.     urlKind = kNewsURL;
  627.     if (IsSlackNewsURL(text, begin, end, url)) goto exit;
  628.     return kNotURL;
  629.     
  630. exit:
  631.  
  632.     /* Strip line breaks and white space around line breaks from the url. */
  633.  
  634.     for (p = q = url; *p != 0; p++) {
  635.         if (*p == CR) {
  636.             q--;
  637.             while (q >= url && isLWSP(*q)) q--;
  638.             q++;
  639.             p++;
  640.             while (*p != 0 && isLWSP(*p)) p++;
  641.             p--;
  642.         } else {
  643.             *q++ = *p;
  644.         }
  645.     }
  646.     *q = 0;
  647.     return urlKind;
  648. }
  649.  
  650.  
  651.  
  652. /*----------------------------------------------------------------------------
  653.     ParseNntpURL
  654.     
  655.     Parse an nntp URL.
  656.     
  657.     Entry:    url = URL string.
  658.     
  659.     Exit:    function result = error code (paramErr if syntax error).
  660.             host = news server host address.
  661.             *port = port number.
  662.             newsgroup = newsgroup name.
  663.             *artNumber = article number.
  664. ----------------------------------------------------------------------------*/
  665.  
  666. static OSErr ParseNntpURL (char *url, char *host, short *port, 
  667.     char *newsgroup, long *artNumber)
  668. {
  669.     char *p, *q;
  670.     
  671.     p = url + strlen("nntp://");
  672.     
  673.     q = host;
  674.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  675.     *q = 0;
  676.     
  677.     if (*p == ':') {
  678.         *port = atoi(p+1);
  679.         while (*p != '/' && *p != 0) p++;
  680.     } else {
  681.         *port = kNNTPPort;
  682.     }
  683.     
  684.     if (*p != '/') return paramErr;
  685.     p++;
  686.     q = newsgroup;
  687.     while (*p != '/' && *p != 0) *q++ = *p++;
  688.     *q = 0;
  689.     
  690.     if (*p != '/') return paramErr;
  691.     *artNumber = atol(p+1);
  692.  
  693.     return noErr;
  694. }
  695.  
  696.  
  697.  
  698. /*----------------------------------------------------------------------------
  699.     CreateAURLFile
  700.     
  701.     Create Fetch or Anarchie AURL file.
  702.     
  703.     Entry:    sig = signature of helper.
  704.             url = URL string.
  705.     
  706.     Exit:    function result = error code.
  707.             *docSpec = file spec of created AURL file.
  708. ----------------------------------------------------------------------------*/
  709.  
  710. static OSErr CreateAURLFile (OSType sig, char *url, FSSpec *docSpec)
  711. {
  712.     OSErr err = noErr;
  713.     short fRefNum = 0, len;
  714.     char cmd[1000];
  715.     long count;
  716.     
  717.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, sig, kFTPHelperURLFileType);
  718.     if (err != noErr) return err;
  719.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  720.     if (err != noErr) return err;
  721.     len = strlen(url);
  722.     sprintf(cmd, "FCH %s", url);
  723.     count = strlen(cmd);
  724.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  725.     MyFSClose(fRefNum, nil);
  726.     return err;
  727. }
  728.  
  729.  
  730.  
  731. /*----------------------------------------------------------------------------
  732.     CreateNCSATelnetSettingsFile
  733.     
  734.     Create an NCSA Telnet settings file.
  735.     
  736.     Entry:    url = URL string.
  737.     
  738.     Exit:    function result = error code.
  739.             *docSpec = file spec of created settings file.
  740. ----------------------------------------------------------------------------*/
  741.  
  742. static OSErr CreateNCSATelnetSettingsFile (char *url, FSSpec *docSpec)
  743. {
  744.     OSErr err = noErr;
  745.     short fRefNum = 0;
  746.     char cmd[1000];
  747.     long count;
  748.     CStr255 hostName;
  749.     short port;
  750.     char *p, *q;
  751.     
  752.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, 
  753.         kNCSATelnetCreatorType, kNCSATelnetSettingsFileType);
  754.     if (err != noErr) return err;
  755.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  756.     if (err != noErr) return err;
  757.     
  758.     p = url + strlen("telnet://");
  759.     q = p;
  760.     while (*q != '@' && *q != 0) q++;
  761.     if (*q == '@') p = q+1;
  762.     q = hostName;
  763.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  764.     *q = 0;
  765.     if (*p == ':') {
  766.         port = atoi(p+1);
  767.     } else {
  768.         port = 23;
  769.     }
  770.     sprintf(cmd, "name=\"%s\"\rhost=\"%s\"\rport=\"%d\"\r", url, hostName, port);
  771.  
  772.     count = strlen(cmd);
  773.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  774.     MyFSClose(fRefNum, nil);
  775.     return err;
  776. }
  777.  
  778.  
  779.  
  780. /*----------------------------------------------------------------------------
  781.     CreateTn3270SettingsFile
  782.     
  783.     Create a tn3270 settings file.
  784.     
  785.     Entry:    url = URL string.
  786.     
  787.     Exit:    function result = error code.
  788.             *docSpec = file spec of created settings file.
  789. ----------------------------------------------------------------------------*/
  790.  
  791. static OSErr CreateTn3270SettingsFile (char *url, FSSpec *docSpec)
  792. {
  793.     OSErr err = noErr;
  794.     short fRefNum = 0;
  795.     char cmd[1000];
  796.     long count;
  797.     CStr255 hostName;
  798.     char *p, *q;
  799.     
  800.     err = CreateTemporaryFile(docSpec, kNewsWatcherSignature, 
  801.         ktn3270CreatorType, ktn3270SettingsFileType);
  802.     if (err != noErr) return err;
  803.     err = FSpOpenDF(docSpec, fsRdWrPerm, &fRefNum);
  804.     if (err != noErr) return err;
  805.     
  806.     p = url + strlen("tn3270://");
  807.     q = p;
  808.     while (*q != '@' && *q != 0) q++;
  809.     if (*q == '@') p = q+1;
  810.     q = hostName;
  811.     while (*p != ':' && *p != '/' && *p != 0) *q++ = *p++;
  812.     *q = 0;
  813.     sprintf(cmd, "host_name=\"%s\"\rwindow_title=\"%s\"\r", hostName, url);
  814.  
  815.     count = strlen(cmd);
  816.     err = MyFSWriteNoCache(fRefNum, &count, cmd, nil);
  817.     MyFSClose(fRefNum, nil);
  818.     return err;
  819. }
  820.  
  821.  
  822.  
  823. /*----------------------------------------------------------------------------
  824.     OpenHelperWithURL
  825.     
  826.     Open a helper program and pass it a URL string.
  827.     
  828.     Entry:    helperInfo = pointer to helper info.
  829.             url = URL string.
  830.             foreground = true to run helper in foreground, false to run it
  831.                 in the background.
  832.     
  833.     Exit:    function result = error code.
  834. ----------------------------------------------------------------------------*/
  835.  
  836. static OSErr OpenHelperWithURL (TURLHelperInfo *helperInfo, CStr255 url,
  837.     Boolean foreground)
  838. {
  839.     OSErr err = noErr;
  840.     ProcessSerialNumber psn;
  841.     FSSpec appSpec, docSpec;
  842.     Boolean running;
  843.     unsigned short launchControlFlags;
  844.     char hackedURL[500];
  845.     unsigned long lastMod;
  846.     
  847.     launchControlFlags = launchContinue | launchNoFileFlags;
  848.     if (!foreground) launchControlFlags |= launchDontSwitch;
  849.         
  850.     err = FindAppFromSig(helperInfo->sig, &appSpec, &running, &psn);
  851.     if (err != noErr) goto exit;
  852.     
  853.     err = GetLastModDateTime(&appSpec, &lastMod);
  854.     if (err != noErr) goto exit;
  855.     if (lastMod != helperInfo->lastMod) {
  856.         if (!ValidURLHelper(&appSpec, helperInfo)) return userCanceledErr;
  857.     }
  858.     
  859.     switch (helperInfo->sig) {
  860.     
  861.         case kFetchCreatorType:
  862.         
  863.             err = CreateAURLFile(helperInfo->sig, url, &docSpec);
  864.             if (err != noErr) goto exit;
  865.             err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  866.                 launchControlFlags);
  867.             break;
  868.  
  869.         case kAnarchieCreatorType:
  870.  
  871.             if (helperInfo->versionNumber >= kAnarchieMinVersionNumberGURL) {
  872.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  873.                     kGetURLEventClass, kGetURLEventID,
  874.                     keyDirectObject, url, 0, launchControlFlags);
  875.             } else {
  876.                 err = CreateAURLFile(helperInfo->sig, url, &docSpec);
  877.                 if (err != noErr) goto exit;
  878.                 err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  879.                     launchControlFlags);
  880.             }
  881.             break;
  882.             
  883.         case kMacWebCreatorType:
  884.         case kMacWAISCreatorType:
  885.         
  886.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  887.                 kMacWebAndWAISOpenURLEventClass, kMacWebAndWAISOpenURLEventID,
  888.                 kMacWebAndWAISOpenURLKeyword, url, 0, launchControlFlags);
  889.             break;
  890.             
  891.         case kNCSAMosaicCreatorType:
  892.         
  893.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  894.                 kNCSAMosaicOpenURLEventClass, kNCSAMosaicOpenURLEventID,
  895.                 keyDirectObject, url, 0, launchControlFlags);
  896.             break;
  897.             
  898.         case kTurboGopherCreatorType:
  899.  
  900.             if (helperInfo->versionNumber < kTurboGopherMinVersionNumberCanon) {
  901.                 /* Special case for version 2.0a1 - must add <URL:...> wrapper. */
  902.                 if (MyStrNEqual(url, "<URL:", 5)) {
  903.                     strcpy(hackedURL, url);
  904.                 } else if (MyStrNEqual(url, "URL:", 4)) {
  905.                     sprintf(hackedURL, "<%s>", url);
  906.                 } else {
  907.                     sprintf(hackedURL, "<URL:%s>", url);
  908.                 }
  909.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  910.                     kGetURLEventClass, kGetURLEventID,
  911.                     keyDirectObject, hackedURL, 0, launchControlFlags);
  912.             } else {
  913.                 /* Versions >= 2.0b1 accept canoncial form without wrapper. */
  914.                 err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  915.                     kGetURLEventClass, kGetURLEventID,
  916.                     keyDirectObject, url, 0, launchControlFlags);
  917.             }
  918.             break;
  919.             
  920.         case kNCSATelnetCreatorType:
  921.         
  922.             err = CreateNCSATelnetSettingsFile(url, &docSpec);
  923.             if (err != noErr) goto exit;
  924.             err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  925.                 launchControlFlags);
  926.             break;
  927.             
  928.         case ktn3270CreatorType:
  929.         
  930.             err = CreateTn3270SettingsFile(url, &docSpec);
  931.             if (err != noErr) goto exit;
  932.             err = LaunchAppWithDoc(running, &appSpec, &psn, &docSpec, 0, 
  933.                 launchControlFlags);
  934.             break;
  935.             
  936.         default:
  937.         
  938.             err = LaunchAppWithEventAndString(running, &appSpec, &psn,
  939.                 kGetURLEventClass, kGetURLEventID,
  940.                 keyDirectObject, url, 0, launchControlFlags);
  941.             break;
  942.             
  943.     }
  944.     
  945.     if (err != noErr) goto exit;
  946.     
  947.     return noErr;
  948.     
  949. exit:
  950.  
  951.     return HelperErrorMessage(err, helperInfo->sig);
  952. }
  953.  
  954.  
  955.  
  956. /*----------------------------------------------------------------------------
  957.     FlashObject
  958.     
  959.     Flash an object in an article, message, or text window.
  960.     
  961.     Entry:    theTE = handle to TextEdit record.
  962.             start = starting position of object.
  963.             end = ending position of object.
  964. ----------------------------------------------------------------------------*/
  965.  
  966. static void FlashObject (TEHandle theTE, short start, short end)
  967. {
  968.     short i;
  969.     long finalTick;
  970.     
  971.     TESetSelect(start, end, theTE);
  972.     for (i = 0; i < 2; i++) {
  973.         Delay(5, &finalTick);
  974.         TEDeactivate(theTE);
  975.         Delay(5, &finalTick);
  976.         TEActivate(theTE);
  977.     }
  978. }
  979.  
  980.  
  981.  
  982. /*----------------------------------------------------------------------------
  983.     ProcessURLEscapeCodes
  984.     
  985.     Process "%xx" escape codes in a URL string (replace them by the characters
  986.     they represent).
  987.     
  988.     Entry:    url = URL with escape codes.
  989.             
  990.     Exit:    url = URL with escape codes replaced by the characters they
  991.                 represent.
  992. ----------------------------------------------------------------------------*/
  993.  
  994. static void ProcessURLEscapeCodes (char *url)
  995. {
  996.     char *p, *q;
  997.     char c1, c2;
  998.     
  999.     p = q = url;
  1000.     while (*p != 0) {
  1001.         if (*p == '%') {
  1002.             c1 = tolower(*(p+1));
  1003.             c2 = tolower(*(p+2));
  1004.             if (isxdigit(c1) && isxdigit(c2)) {
  1005.                 c1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
  1006.                 c2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
  1007.                 *q++ = (c1 << 4) + c2;
  1008.                 p += 3;
  1009.             } else {
  1010.                 *q++ = *p++;
  1011.             }
  1012.         } else {
  1013.             *q++ = *p++;
  1014.         }
  1015.     }
  1016.     *q = 0;
  1017. }
  1018.  
  1019.  
  1020.  
  1021. /*----------------------------------------------------------------------------
  1022.     OpenNewsURL
  1023.     
  1024.     Open a news URL or slack URL.
  1025.     
  1026.     Entry:    url = news URL or slack URL.
  1027.             
  1028.     Exit:    function result = error code (fnfErr if article or group not found).
  1029. ----------------------------------------------------------------------------*/
  1030.  
  1031. static OSErr OpenNewsURL (char *url)
  1032. {
  1033.     char *p;
  1034.     Cell theCell;
  1035.     Boolean hasArts;
  1036.  
  1037.     if (MyStrNEqual(url, "news:", 5)) {
  1038.         if (url[5] == '*' && url[6] == 0) {
  1039.             /* Show full group list window */
  1040.             if (((WindowPeek)gFullGroupWindow)->visible) {
  1041.                 MySelectWindow(gFullGroupWindow);
  1042.                 return noErr;
  1043.             } else {
  1044.                 return DoShowHideFullGroupList();
  1045.             }
  1046.         } else {
  1047.             for (p = url; *p != 0; p++)
  1048.                 if (*p == '@')
  1049.                     return OpenReferencedArticle(url);
  1050.             /* Open group from full group list */
  1051.             theCell.h = 0;
  1052.             theCell.v = FindGroupIndex(url+5);
  1053.             if (theCell.v == -1) return fnfErr;
  1054.             return OpenGroupCell(gFullGroupWindow, theCell, gPrefs.maxFetch, &hasArts);
  1055.         }
  1056.     } else {
  1057.         return OpenReferencedArticle(url);
  1058.     }
  1059.     return noErr;
  1060. }
  1061.  
  1062.  
  1063.  
  1064. /*----------------------------------------------------------------------------
  1065.     OpenURL
  1066.     
  1067.     Open a URL.
  1068.     
  1069.     Entry:    wind = pointer to article, message, or text window.
  1070.             theTE = handle to text edit record.
  1071.             begin = offset in text of beginning of url.
  1072.             end = offset in text of end of url.
  1073.             
  1074.     Exit:    function result = error code.
  1075. ----------------------------------------------------------------------------*/
  1076.  
  1077. static OSErr OpenURL (WindowPtr wind, TEHandle theTE, short begin, short end)
  1078. {
  1079.     TWindow **info;
  1080.     CStr255 url, msg;
  1081.     short urlBegin, urlEnd;
  1082.     TURLKind urlKind;
  1083.     TURLHelperInfoPtr helperInfo;
  1084.     Boolean foreground;
  1085.     CStr255 host, newsgroup;
  1086.     short port;
  1087.     long artNumber;
  1088.     OSErr err = noErr;
  1089.     
  1090.     info = (TWindow**)GetWRefCon(wind);
  1091.     urlKind = ParseURL((**theTE).hText, begin, end, url, &urlBegin, &urlEnd);
  1092.     if (urlKind == kNotURL) {
  1093.         SysBeep(0);
  1094.         return userCanceledErr;
  1095.     }
  1096.     FlashObject(theTE, urlBegin, urlEnd);
  1097.     
  1098.     switch (urlKind) {
  1099.     
  1100.         case kMailtoURL:
  1101.         
  1102.             ProcessURLEscapeCodes(url);
  1103.             return OpenMailWindow(url);
  1104.             
  1105.         case kNewsURL:
  1106.         
  1107.             ProcessURLEscapeCodes(url);
  1108.             err = OpenNewsURL(url);
  1109.             if (err == fnfErr) {
  1110.                 NoteMessageNumber(kStrArticleOrGroupNotFound);
  1111.                 return userCanceledErr;
  1112.             } else {
  1113.                 return err;
  1114.             }
  1115.             
  1116.         case kNntpURL:
  1117.         
  1118.             ProcessURLEscapeCodes(url);
  1119.             err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
  1120.             if (err == paramErr) {
  1121.                 ErrorMessageNumber(kStrNntpURLSyntaxError);
  1122.                 return userCanceledErr;
  1123.             } else if (err != noErr) {
  1124.                 return err;
  1125.             }
  1126.             err = OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
  1127.             if (err == fnfErr) {
  1128.                 NoteMessageNumber(kStrArticleOrGroupNotFound);
  1129.                 return userCanceledErr;
  1130.             } else {
  1131.                 return err;
  1132.             }
  1133.             
  1134.         default:
  1135.         
  1136.             switch (urlKind) {
  1137.                 case kFtpURL:
  1138.                     MyICReadSharedPrefs(kICftpHelper);
  1139.                     break;
  1140.                 case kHttpURL:
  1141.                     MyICReadSharedPrefs(kIChttpHelper);
  1142.                     break;
  1143.                 case kGopherURL:
  1144.                     MyICReadSharedPrefs(kICgopherHelper);
  1145.                     break;
  1146.                 case kWaisURL:
  1147.                     MyICReadSharedPrefs(kICwaisHelper);
  1148.                     break;
  1149.                 case kTelnetURL:
  1150.                     MyICReadSharedPrefs(kICtelnetHelper);
  1151.                     break;
  1152.                 case kTn3270URL:
  1153.                     MyICReadSharedPrefs(kICtn3270Helper);
  1154.                     break;
  1155.                 case kFingerURL:
  1156.                     MyICReadSharedPrefs(kICfingerHelper);
  1157.                     break;
  1158.                 case kWhoisURL:
  1159.                     MyICReadSharedPrefs(kICwhoisHelper);
  1160.                     break;
  1161.             }
  1162.             helperInfo = gURLInfo[urlKind];
  1163.             foreground = urlKind != kFtpURL || url[strlen(url)-1] == '/';
  1164.             if (FindURLHelper(urlKind)) {
  1165.                 return OpenHelperWithURL(helperInfo, url, foreground);
  1166.             } else {
  1167.                 GetCString(kStrCantFindDefaultURLHelper, msg);
  1168.                 DoCantFindHelperDialog(msg);
  1169.                 return userCanceledErr;
  1170.             }
  1171.                 
  1172.     }
  1173. }
  1174.  
  1175.  
  1176.  
  1177. /*----------------------------------------------------------------------------
  1178.     CommandClick
  1179.     
  1180.     Handle a command-click in an article, message, or text window.
  1181.     
  1182.     Entry:    wind = pointer to article, message, or text window.
  1183.             theTE = handle to TextEdit record in which command-click occurred.
  1184.             oldSelStart = start of selection range before user command-clicked.
  1185.             oldSelEnd = end of selection range before user command-clicked.
  1186.             modifiers = modifiers field from event record.
  1187.             
  1188.     Exit:    function result = error code.
  1189. ----------------------------------------------------------------------------*/
  1190.  
  1191. OSErr CommandClick (WindowPtr wind, TEHandle theTE, 
  1192.     short oldSelStart, short oldSelEnd, short modifiers)
  1193. {
  1194.     TWindow **info;
  1195.     short selStart, selEnd, begin, end;
  1196.     DialogPtr dlg;
  1197.     short item;
  1198.     OSErr err = noErr;
  1199.     
  1200.     if (!gStartupOK) return noErr;
  1201.     
  1202.     if ((modifiers & optionKey) == 0 && (modifiers & cmdKey) == 0) return noErr;
  1203.     
  1204.     if ((modifiers & cmdKey) == 0) {
  1205.         err = MyGetNewDialog(kOptionClickDlg, ok, cancel, &dlg);
  1206.         SysBeep(0);
  1207.         if (err != noErr) return err;
  1208.         MyModalDialog(dlg, gDialogFilterUPP, &item);
  1209.         DoClose(dlg);
  1210.         if (item == cancel) return userCanceledErr;
  1211.     }
  1212.         
  1213.     info = (TWindow**)GetWRefCon(wind);
  1214.     selStart = (**theTE).selStart;
  1215.     selEnd = (**theTE).selEnd;
  1216.     if (selStart != selEnd) return noErr;
  1217.     
  1218.     if (oldSelStart < oldSelEnd && oldSelStart <= selStart && 
  1219.         selStart <= oldSelEnd) 
  1220.     {
  1221.         begin = oldSelStart;
  1222.         end = oldSelEnd;
  1223.     } else {
  1224.         begin = end = selStart;
  1225.     }
  1226.     
  1227.     return OpenURL(wind, theTE, begin, end);
  1228. }
  1229.  
  1230.  
  1231.  
  1232. /*----------------------------------------------------------------------------
  1233.     IsURL
  1234.     
  1235.     Check to see if the current selected text in a text edit record is
  1236.     a URL.
  1237.     
  1238.     Entry:    theTE = handle to TextEdit record.
  1239.             
  1240.     Exit:    function result = true if current selection is a URL.
  1241. ----------------------------------------------------------------------------*/
  1242.  
  1243. Boolean IsURL (TEHandle theTE)
  1244. {
  1245.     CStr255 url;
  1246.     short urlBegin, urlEnd;
  1247.  
  1248.     return ParseURL((**theTE).hText, (**theTE).selStart, (**theTE).selEnd,
  1249.         url, &urlBegin, &urlEnd) != kNotURL;
  1250. }
  1251.  
  1252.  
  1253.  
  1254. /*----------------------------------------------------------------------------
  1255.     DoOpenURL
  1256.     
  1257.     Handle the "Open URL" command.
  1258.     
  1259.     Entry:    wind = pointer to article, message, or text window.
  1260.             
  1261.     Exit:    function result = error code.
  1262. ----------------------------------------------------------------------------*/
  1263.  
  1264. OSErr DoOpenURL (WindowPtr wind)
  1265. {
  1266.     TWindow **info;
  1267.     TEHandle theTE;
  1268.     TMsgFieldInfo **fields;
  1269.     short curField;
  1270.     
  1271.     info = (TWindow**)GetWRefCon(wind);
  1272.     if ((**info).kind == kMessage) {
  1273.         fields = (**info).fields;
  1274.         curField = (**info).curField;
  1275.         theTE = (*fields)[curField].edit;
  1276.     } else {
  1277.         theTE = (**info).theTE;
  1278.     }
  1279.     return OpenURL(wind, theTE, (**theTE).selStart, (**theTE).selEnd);
  1280. }
  1281.  
  1282.  
  1283.  
  1284. /*----------------------------------------------------------------------------
  1285.     OpenURLString
  1286.     
  1287.     Open a news, nntp, or mailto URL.
  1288.     
  1289.     Entry:    urlString = the URL string.
  1290.             
  1291.     Exit:    function result = error code.
  1292. ----------------------------------------------------------------------------*/
  1293.  
  1294. OSErr OpenURLString (char *urlString)
  1295. {
  1296.     OSErr err = noErr;
  1297.     Handle h = nil;
  1298.     short len, urlBegin, urlEnd;
  1299.     CStr255 url;
  1300.     TURLKind urlKind;
  1301.     CStr255 host, newsgroup;
  1302.     short port;
  1303.     long artNumber;
  1304.  
  1305.     len = strlen(urlString);
  1306.     err = MyPtrToHand(urlString, &h, len);
  1307.     if (err != noErr) return err;
  1308.     urlKind = ParseURL(h, 0, len, url, &urlBegin, &urlEnd);
  1309.     MyDisposeHandle(h);
  1310.     ProcessURLEscapeCodes(url);
  1311.     
  1312.     switch (urlKind) {
  1313.     
  1314.         case kMailtoURL:
  1315.         
  1316.             return OpenMailWindow(url);
  1317.             
  1318.         case kNewsURL:
  1319.         
  1320.             return OpenNewsURL(url);
  1321.             
  1322.         case kNntpURL:
  1323.         
  1324.             err = ParseNntpURL(url, host, &port, newsgroup, &artNumber);
  1325.             if (err != noErr) return err;
  1326.             return OpenArticleOnAnyServer(host, port, newsgroup, artNumber);
  1327.             
  1328.         default:
  1329.         
  1330.             return paramErr;
  1331.             
  1332.     }
  1333. }
  1334.